﻿//#define ASSERT
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace GameToolkit
{
    public interface IMaterialPropertyBlock
    {
        void Clear();
        void SetColor(int id, Color color);
        void SetFloat(int id, float value);
        void SetVector(int id, Vector4 vector);
        void SetTexture(int id, Texture tex);
        void SetInt(int id, int value);
        void EnableKeyword(string keyword);
        void DisableKeyword(string keyword);
    }

    public interface IMaterialInstance : IMaterialPropertyBlock
    {
        int GroupId { get; }
        Material sharingMaterial { get; }
        Material material { get; }
        void Retain();
        void Release();
    }

    /// <summary>
    /// 实现该接口以设置 MaterialOverrider 属性
    /// </summary>
    public interface ICbufferBlock : IComponent
    {
        //Transform transform { get; }
        void OnBinding(MaterialGroupOverrider overrider);
        void OnUnbinding(MaterialGroupOverrider overrider);
        void OnSetupMaterial(Renderer renderer, int materialIndex, IMaterialPropertyBlock block);
    }

    /// <summary>
    /// 实现该接口以控制 MaterialOverrider 功能开启与关闭
    /// </summary>
    public interface ICbufferBlockFeature
    {
        bool IsFeatureOn { get; }
    }

    public static class MaterialPropertyBlockExtensions
    {
        public static void SetColor(this IMaterialPropertyBlock mat, string name, Color color)
        {
            mat.SetColor(Shader.PropertyToID(name), color);
        }
        public static void SetFloat(this IMaterialPropertyBlock mat, string name, float value)
        {
            mat.SetFloat(Shader.PropertyToID(name), value);
        }
        public static void SetVector(this IMaterialPropertyBlock mat, string name, Vector4 vec)
        {
            mat.SetVector(Shader.PropertyToID(name), vec);
        }
        public static void SetTexture(this IMaterialPropertyBlock mat, string name, Texture tex)
        {
            mat.SetTexture(Shader.PropertyToID(name), tex);
        }
        public static void SetInt(this IMaterialPropertyBlock mat, string name, int value)
        {
            mat.SetInt(Shader.PropertyToID(name), value);
        }

    }

    /// <summary>
    /// 开启材质覆盖功能的根节点
    /// </summary>
    [DisallowMultipleComponent, ExecuteAlways]
    public sealed class MaterialGroupOverrider : MonoBehaviour
    {
        public enum EFeatureOn
        {
            Off,
            Auto,
            Always,
        }

//#if UNITY_EDITOR
//        static bool IsAlwaysExecute(System.Type type)
//        {
//            var att = type.GetCustomAttributes(true);
//            var exe1 = typeof(ExecuteAlways);
//            var exe2 = typeof(ExecuteInEditMode);
//            for (int i = 0; i < att.Length; i++)
//            {
//                var exe = att[i].GetType();
//                if (exe == exe1 || exe == exe2)
//                    return true;
//            }
//            return false;
//        }
//#endif

        internal abstract class Inst : IMaterialPropertyBlock
        {
            internal readonly int id;
            uint refers;
            public Inst(int id)
            {
                this.id = id;
            }
            public uint References => refers;
            public abstract void Clear();
            public abstract void SetColor(int id, Color color);
            public abstract void SetInt(int id, int value);
            public abstract void SetFloat(int id, float value);
            public abstract void SetTexture(int id, Texture tex);
            public abstract void SetVector(int id, Vector4 vector);
            public abstract void EnableKeyword(string keyword);
            public abstract void DisableKeyword(string keyword);
            protected abstract void OnRelease();
            protected virtual bool AssertCondition => false;
            protected virtual string AssertName => "MatInst";
            public void Retain()
            {
                lock (MaterialCache._lock)
                {
                    refers++;
#if ASSERT
                    if (AssertCondition)
                        Debug.LogFormat("[Material] retain \"{0}\" (ref:{1})", AssertName, refers);
#endif
                }
            }
            public void Release()
            {
                lock (MaterialCache._lock)
                {
                    if (refers == 0)
                        throw new System.Exception("Material Instance was already released.");
                    refers--;
#if ASSERT
                    if (AssertCondition)
                        Debug.LogFormat("[Material] release \"{0}\" (ref:{1})", AssertName, refers);
#endif
                    if (refers == 0)
                    {
                        OnRelease();
                    }
                }
            }
        }

        //static readonly List<ICbufferBlock> sNoAllocCbufferBlocks = new List<ICbufferBlock>(5);
        //static readonly List<ICbufferBlock> sTempCbufferBlocks = new List<ICbufferBlock>();
        //static readonly List<MaterialBlock> sTempMaterialBlocks = new List<MaterialBlock>();
        static readonly HashSet<Transform> sTempTransforms = new HashSet<Transform>();
        static readonly HashSet<ICbufferBlock> sTempCbuffers = new HashSet<ICbufferBlock>();
        static readonly HashSet<int> sIdCache = new HashSet<int>();
        //[SerializeField, EnableEdit(EEnableEditMode.Disabled)]
        //private Renderer m_RendererPrefab;

        //[SerializeField, EnableEdit(EEnableEditMode.Disabled)]
        //private int m_MaterialCount;

        //Renderer mRenderer;
        ////Material[] mMaterials;
        //ICommand mApplyMaterialCmd;
        //ParallelDispatcher.Handler mApplyHandler;
        //RandomList<Transform> mDirtyTrans = new RandomList<Transform>();

        // 默认是否开启该功能， 如果默认不开启，则需要在包含的 ICbufferBlock 组件中实现 ICbufferBlockFeature, 才可开启此功能
        [SerializeField]
        EFeatureOn m_DefaultFeatureOn;

        MaterialOverrider[] mBlocks = new MaterialOverrider[4];
        int mDirtyCount;
        int mBlockCount;

        bool mIsActive;
        bool mRebindingCbuffer;
        List<ICbufferBlock> mCbuffers = new List<ICbufferBlock>();
        List<ICbufferBlock> mAdditionalCbuffers;
        public int CbufferCount => mCbuffers.Count;
        public int OverrideRendererCount => mBlockCount;
        public ICbufferBlock GetCbuffer(int index)
        {
            return mCbuffers[index];
        }
        public MaterialOverrider GetOverrider(int index)
        {
            return mBlocks[index];
        }

        public void AddCbufferBlock(ICbufferBlock block)
        {
            if (block == null)
                return;
            if (mAdditionalCbuffers == null)
            {
                mAdditionalCbuffers = new List<ICbufferBlock>();
                mAdditionalCbuffers.Add(block);
                mRebindingCbuffer = true;
                if (mIsActive && !MaterialCache.IsDirty(this))
                    MaterialCache.SetDirty(this);
            }
            else if (!mAdditionalCbuffers.Contains(block))
            {
                mAdditionalCbuffers.Add(block);
                mRebindingCbuffer = true;
                if (mIsActive && !MaterialCache.IsDirty(this))
                    MaterialCache.SetDirty(this);
            }
        }

        public void RemoveCbufferBlock(ICbufferBlock block)
        {
            if (block == null)
                return;
            if(mAdditionalCbuffers != null && mAdditionalCbuffers.Remove(block))
            {
                mRebindingCbuffer = true;
                if (mIsActive && !MaterialCache.IsDirty(this))
                    MaterialCache.SetDirty(this);
            }
        }

        internal void SetDirty(MaterialOverrider block)
        {
            if (mIsActive && block != null)
            {
                for (int i = 0; i < mBlockCount; i++)
                {
                    if (mBlocks[i] == block)
                    {
                        mRebindingCbuffer = true;
                        if (i >= mDirtyCount)
                        {
                            if (i > mDirtyCount)
                            {
                                mBlocks[i] = mBlocks[mDirtyCount];
                                mBlocks[mDirtyCount] = block;
                            }
                            mDirtyCount++;
                            if (mDirtyCount == 1)
                                MaterialCache.SetDirty(this);
                            //if (mApplyCmd == null)
                            //    mApplyCmd = new Cmd0(ApplyDirtyBlocks);
                            //MainThread.RunOnMainThread(mApplyCmd, EExecuteEvent.LateUpdate);
                        }
                        return;
                    }
                }
            }
            //if (mIsActive && block != null && mBlocks.Contains(block))
            //{
            //    mRebindingCbuffer = true;
            //    mDirtyBlocks.Add(block);
            //    if (mApplyCmd == null)
            //        mApplyCmd = new Cmd0(ApplyDirtyBlocks);
            //    MainThread.RunOnMainThread(mApplyCmd, EExecuteEvent.LateUpdate);
            //}
        }

        internal void Regist(MaterialOverrider block)
        {
            if (block != null)
            {
                for (int i = 0; i < mBlockCount; i++)
                {
                    if (mBlocks[i] == block)
                        return;
                }
                NativeArrayUtil.EnsureNativeSize(ref mBlocks, mBlockCount, mBlockCount + 1, 2f);
                if (mIsActive)
                {
                    if (mDirtyCount < mBlockCount)
                        mBlocks[mBlockCount] = mBlocks[mDirtyCount];
                    mBlocks[mDirtyCount] = block;
                    mBlockCount++;
                    mDirtyCount++;
                    mRebindingCbuffer = true;
                    //block.OnFeatureEnabled();
                    //if (mApplyCmd == null)
                    //    mApplyCmd = new Cmd0(ApplyDirtyBlocks);
                    //MainThread.RunOnMainThread(mApplyCmd, EExecuteEvent.LateUpdate);
                    if (mDirtyCount == 1)
                        MaterialCache.SetDirty(this);
                }
                else
                {
                    mBlocks[mBlockCount] = block;
                    mBlockCount++;
                }
            }

        }

        internal void Unregist(MaterialOverrider block)
        {
            for (int i = 0; i < mBlockCount; i++)
            {
                if (mBlocks[i] == block)
                {
                    if (mIsActive)
                    {
                        if (i < mDirtyCount)
                        {
                            mDirtyCount--;
                            mBlockCount--;
                            if (i < mDirtyCount)
                                mBlocks[i] = mBlocks[mDirtyCount];
                            if (mDirtyCount < mBlockCount)
                                mBlocks[mDirtyCount] = mBlocks[mBlockCount];
                            mBlocks[mBlockCount] = null;
                        }
                        else
                        {
                            mBlockCount--;
                            if (i < mBlockCount)
                                mBlocks[i] = mBlocks[mBlockCount];
                            mBlocks[mBlockCount] = null;
                        }
                        mRebindingCbuffer = true;
                        block.IsFeatureOn = false;

                        MaterialCache.SetDirty(this);

                        //if (mRebindCmd == null)
                        //    mRebindCmd = new Cmd0(BindCbuffers);
                        //MainThread.RunOnMainThread(mRebindCmd, EExecuteEvent.LateUpdate);
                    }
                    else
                    {
                        mBlockCount--;
                        if (i < mBlockCount)
                            mBlocks[i] = mBlocks[mBlockCount];
                        mBlocks[mBlockCount] = null;
                    }
                    return;
                }
            }
            //if(mBlocks.Remove(block))
            //{
            //    if (mIsActive)
            //    {
            //        block.Inactivite();
            //        mDirtyBlocks.Remove(block);
            //        mRebindingCbuffer = true;
            //        if (mRebindCmd == null)
            //            mRebindCmd = new Cmd0(BindCbuffers);
            //        MainThread.RunOnMainThread(mRebindCmd, EExecuteEvent.LateUpdate);
            //    }
            //    //SetDirty(block.transform.parent);
            //}
        }

        // 标记材质节点改变
        public void SetDirty(Transform dirty)
        {
            if (!mIsActive || mDirtyCount == mBlockCount || dirty == null || !dirty.IsChildOf(transform))
                return;
            var dirtys = mDirtyCount;
            for (int i = mDirtyCount; i < mBlockCount; i++)
            {
                var mt = mBlocks[i];
                if (mt.transform.IsChildOf(dirty))
                {
                    if (i > mDirtyCount)
                    {
                        mBlocks[i] = mBlocks[mDirtyCount];
                        mBlocks[mDirtyCount] = mt;
                    }
                    mDirtyCount++;
                }
            }
            if (dirtys < mDirtyCount && dirtys == 0)
            {
                MaterialCache.SetDirty(this);
                //if (mApplyCmd == null)
                //    mApplyCmd = new Cmd0(ApplyDirtyBlocks);
                //MainThread.RunOnMainThread(mApplyCmd, EExecuteEvent.LateUpdate);
            }
        }

        private void OnEnable()
        {
            mRebindingCbuffer = mBlockCount > 0;
            mDirtyCount = mBlockCount;
            //for (int i = 0; i < mBlockCount; i++)
            //{
            //    var block = mBlocks[i];
            //    if (block != null)
            //    {
            //        block.OnFeatureEnabled();
            //    }
            //}
            mIsActive = true;
            MaterialCache.SetDirty(this);
            //if (mApplyCmd == null)
            //    mApplyCmd = new Cmd0(ApplyDirtyBlocks);
            //MainThread.RunOnMainThread(mApplyCmd, EExecuteEvent.LateUpdate);
        }

        private void OnDisable()
        {
            mIsActive = false;
            mRebindingCbuffer = false;
            mDirtyCount = 0;
            for (int i = 0; i < mCbuffers.Count; i++)
            {
                mCbuffers[i].OnUnbinding(this);
            }
            mCbuffers.Clear();
            for (int i = 0; i < mBlockCount; i++)
            {
                var block = mBlocks[i];
                if (block != null)
                    block.IsFeatureOn = false;
                    //block.OnFeatureDisabled();
            }
        }
        
        void BindCbuffers()
        {
            if (mRebindingCbuffer)
            {
                mRebindingCbuffer = false;
                sTempCbuffers.Clear();
                sTempTransforms.Clear();
                var tmpBlocks = CacheUtil.GetListCahce<ICbufferBlock>();
                var allBlocks = CacheUtil.GetListCahce<ICbufferBlock>(1);
                if (mAdditionalCbuffers != null)
                    allBlocks.AddRange(mAdditionalCbuffers);
                var root = transform;
                for (int i = 0; i < mBlockCount; i++)
                {
                    var mt = mBlocks[i];
                    if (mt == null)
                        continue;
                    var trans = mt.transform;
                    while (trans != null && trans.IsChildOf(root))
                    {
                        if (sTempTransforms.Add(trans))
                        {
                            trans.GetComponents(tmpBlocks);
                            trans = trans.parent;
                            if (tmpBlocks.Count > 0)
                            {
                                allBlocks.AddRange(tmpBlocks);
                                tmpBlocks.Clear();
                            }
                        }
                        else
                        {
                            break;
                        }
                    }
                }
                for (int i = allBlocks.Count - 1; i >= 0; i--)
                {
                    var blcok = allBlocks[i];
                    if (blcok == null || !sTempCbuffers.Add(blcok))
                        allBlocks.RemoveAt(i);
                }
                for (int i = 0; i < mCbuffers.Count; i++)
                {
                    var cb = mCbuffers[i];
                    if (!sTempCbuffers.Contains(cb))
                        cb.OnUnbinding(this);
                }
                mCbuffers.Clear();
                mCbuffers.AddRange(allBlocks);
                for (int i = 0; i < mCbuffers.Count; i++)
                {
                    mCbuffers[i].OnBinding(this);
                }
                sTempCbuffers.Clear();
            }
        }

        // 刷新材质属性
        internal void ApplyDirtyBlocks()
        {
            if (!mIsActive || this == null)
                return;
            BindCbuffers();
            //var root = transform;
            var lst = CacheUtil.GetListCahce<MaterialOverrider>();
            for (int i = 0; i < mDirtyCount; i++)
            {
                var mt = mBlocks[i];
                if (mt != null)
                {
                    bool featureOn = false;
                    bool autoFeatureOn = EFeatureOn.Auto == m_DefaultFeatureOn;
                    bool hasCbuffer = false;
                    for (int k = mCbuffers.Count - 1; k >= 0; k--)
                    {
                        var cb = mCbuffers[k];
//#if UNITY_EDITOR
                        if (!cb.IsValidInstance())
                        {
                            mRebindingCbuffer = true;
                            mCbuffers.RemoveAt(k);
                            continue;
                        }
//#endif
                        if (mt.transform.IsChildOf(cb.transform))
                        {
                            hasCbuffer = true;
                            if (m_DefaultFeatureOn == EFeatureOn.Always)
                            {
                                featureOn = true;
                            }
                            else if (cb is ICbufferBlockFeature)
                            {
                                featureOn = ((ICbufferBlockFeature)cb).IsFeatureOn;
                                autoFeatureOn = featureOn;
                            }
                            if (featureOn)
                                break;
                        }
                    }

                    mt.IsFeatureOn = hasCbuffer && (autoFeatureOn || featureOn);
                    if (mt.IsFeatureOn)
                    {
                        lst.Add(mt);
                    }
                }
            }
            for (int i = 0; i < lst.Count; i++)
            {
                lst[i].ResetMaterialCbuffer();
            }
            for (int i = mCbuffers.Count - 1; i >= 0; i--)
            {
                var cb = mCbuffers[i];
                try
                {
                    for (int k = 0; k < lst.Count; k++)
                    {
                        var mt = lst[k];
                        if (mt.transform.IsChildOf(cb.transform))
                            mt.SetupMaterialCbuffer(cb, sIdCache);
                    }
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                }
                sIdCache.Clear();
            }
            for (int i = 0; i < lst.Count; i++)
            {
                lst[i].SubmitMaterialCbuffers();
            }
            mDirtyCount = 0;
            sTempCbuffers.Clear();
            sTempTransforms.Clear();
        }

#if UNITY_EDITOR
        private void OnValidate()
        {
            SetDirty(transform);
        }
#endif
    }
}